package com.xyzwps.lib.express.router.ap;

import com.xyzwps.lib.express.router.annotation.openapi.SchemaFrom;
import com.xyzwps.lib.express.router.annotation.rest.*;
import com.xyzwps.lib.express.router.ap.util.FromTypeMirror;
import com.xyzwps.lib.openapi.element.In;
import com.xyzwps.lib.openapi.element.Schema;
import io.avaje.validation.constraints.*;
import lombok.NonNull;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static com.xyzwps.lib.dollar.Dollar.*;

public final class ApiMethodArguments implements Iterable<ApiMethodArgument> {

    private final List<ApiMethodArgument> arguments = new ArrayList<>();

    private boolean hasBody = false;
    private boolean hasRequest = false;
    private boolean hasResponse = false;

    public void add(ApiMethodArgument arg) {
        switch (arg) {
            case ApiMethodArgument.RequestParam it -> arguments.add(it);
            case ApiMethodArgument.Body it -> {
                if (hasBody) throw new IllegalStateException("Duplicate body parameter");
                arguments.add(it);
                hasBody = true;
            }
            case ApiMethodArgument.Request it -> {
                if (hasRequest) throw new IllegalStateException("Duplicate request parameter");
                arguments.add(it);
                hasRequest = true;
            }
            case ApiMethodArgument.Response it -> {
                if (hasResponse) throw new IllegalStateException("Duplicate response parameter");
                arguments.add(it);
                hasResponse = true;
            }
        }
    }


    public static ApiMethodArguments from(ExecutableElement method) {
        var arguments = new ApiMethodArguments();
        for (var param : method.getParameters()) {

            // TODO: 处理这些参数
            var isNotBlank = param.getAnnotation(NotBlank.class) != null;
            var isNotEmpty = isNotBlank || param.getAnnotation(NotEmpty.class) != null;
            var isNotNull = isNotEmpty || param.getAnnotation(NotNull.class) != null;
            var shouldBeNull = param.getAnnotation(Null.class) != null;

            var rp = param.getAnnotation(RequestParam.class);
            if (rp != null) {
                arguments.add(new ApiMethodArgument.RequestParam(rp.name(), typeCanonicalName(param.asType()))
                        .description($.isEmpty(rp.description()) ? null : rp.description())
                        .in($.defaultTo(rp.in(), In.QUERY))
                        .schema(fromRequestParam(rp)));
                continue;
            }

            var bodyParam = param.getAnnotation(Body.class);
            if (bodyParam != null) {
                arguments.add(new ApiMethodArgument.Body(typeCanonicalName(param.asType())));
                continue;
            }
            if (typeCanonicalName(param.asType()).equals("com.xyzwps.lib.express.HttpRequest")) {
                arguments.add(new ApiMethodArgument.Request());
                continue;
            }
            if (typeCanonicalName(param.asType()).equals("com.xyzwps.lib.express.HttpResponse")) {
                arguments.add(new ApiMethodArgument.Response());
                continue;
            }

            throw new IllegalStateException("Unsupported parameter type: " + typeCanonicalName(param.asType()));
        }
        return arguments;
    }

    private static Schema fromRequestParam(RequestParam rp) {
        var asString = rp.asString();
        if (!asString.ignored()) {
            return SchemaFrom.from(asString);
        }

        var asInt = rp.asInteger();
        if (!asInt.ignored()) {
            return SchemaFrom.from(asInt);
        }

        return null;
    }

    private static String typeCanonicalName(TypeMirror mirror) {
        return FromTypeMirror.canonicalName(mirror);
    }

    @Override
    @NonNull
    public Iterator<ApiMethodArgument> iterator() {
        return arguments.iterator();
    }
}
